﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading.Tasks;

namespace Utils.LambdaExtend
{
    public class ChloeLambdaExtend
    {
        private static Expression MakeBinaryExpression(Expression member, object obj, bool isRecursion = false)
        {
            Expression expression = null;
            foreach (var property in obj.GetType().GetProperties())
            {
                object value = property.GetValue(obj);
                Type valueType = property.PropertyType;
                if (value != null)
                {
                    if (valueType.IsGenericType && valueType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
                    {
                        value = valueType.GetProperty("Value").GetValue(value);
                        valueType = valueType.GetGenericArguments()[0];
                    }
                    string name = property.Name;
                    if (name.EndsWith("Gt"))
                    {
                        name = name.Substring(0, name.Length - 2);

                        expression = And(expression, Expression.GreaterThanOrEqual(Expression.Property(member, name), Expression.Constant(value, valueType)));
                    }
                    else if (name.EndsWith("Lt"))
                    {
                        name = name.Substring(0, name.Length - 2);
                        expression = And(expression, Expression.LessThanOrEqual(Expression.Property(member, name), Expression.Constant(value, valueType)));
                    }
                    else if (valueType.Equals(typeof(string)) && name.EndsWith("Lk"))
                    {
                        expression = And(expression, Expression.NotEqual(Expression.Call(Expression.Constant(value, valueType), LambdaMethods.StringContainsMethod, Expression.Property(member, name)), Expression.Constant(-1)));
                    }
                    else if (property.PropertyType.IsArray && !property.PropertyType.GetElementType().Equals(typeof(byte)))
                    {
                        var prop = member.Type.GetProperty(name);
                        var method = LambdaMethods.ArrayContainsMethod.MakeGenericMethod(prop.PropertyType);
                        expression = And(expression, Expression.Call(method, Expression.Constant(value, valueType), Expression.Property(member, prop)));
                    }
                    else if (!isRecursion)
                    {
                        expression = And(expression, Expression.Equal(Expression.Property(member, name), Expression.Constant(value, valueType)));
                    }
                    else
                    {
                        if (valueType.IsPrimitive || valueType.Equals(typeof(String)))
                        {
                            expression = And(expression, Expression.Equal(Expression.Property(member, name), Expression.Constant(value, valueType)));
                        }
                        else
                        {
                            expression = MakeBinaryExpression(Expression.Property(member, property), value);
                        }
                    }
                }
            }
            return expression;
        }

        public static Expression And(Expression e1, Expression e2)
        {
            return e1 == null ? e2 : Expression.And(e1, e2);
        }

        public static List<T> ConvertTableToList<T>(DataTable table) where T : class, new()
        {
            if (table == null)
            {
                return null;
            }
            if (table.Rows.Count < 1)
            {
                return new List<T>();
            }

            var builder = CreateBuilder<T>(table.Rows[0]);
            return table.Rows.Cast<DataRow>().Select(a => builder(a)).ToList();
        }

        public static Func<DataRow, Entity> CreateBuilder<Entity>(DataRow dataRecord)
        {
            DynamicMethod method = new DynamicMethod("DynamicCreateEntity", typeof(Entity), new Type[] { typeof(DataRow) }, typeof(Entity), true);
            ILGenerator generator = method.GetILGenerator();
            LocalBuilder result = generator.DeclareLocal(typeof(Entity));
            generator.Emit(OpCodes.Newobj, typeof(Entity).GetConstructor(Type.EmptyTypes));
            generator.Emit(OpCodes.Stloc, result);
            for (int i = 0; i < dataRecord.ItemArray.Length; i++)
            {
                PropertyInfo propertyInfo = typeof(Entity).GetProperty(dataRecord.Table.Columns[i].ColumnName);
                Label endIfLabel = generator.DefineLabel();
                if (propertyInfo != null && propertyInfo.GetSetMethod() != null)
                {
                    generator.Emit(OpCodes.Ldarg_0);
                    generator.Emit(OpCodes.Ldc_I4, i);
                    generator.Emit(OpCodes.Callvirt, LambdaMethods.isDBNullMethod);
                    generator.Emit(OpCodes.Brtrue, endIfLabel);
                    generator.Emit(OpCodes.Ldloc, result);
                    generator.Emit(OpCodes.Ldarg_0);
                    generator.Emit(OpCodes.Ldc_I4, i);
                    generator.Emit(OpCodes.Callvirt, LambdaMethods.getValueMethod);
                    generator.Emit(OpCodes.Unbox_Any, propertyInfo.PropertyType);
                    generator.Emit(OpCodes.Callvirt, propertyInfo.GetSetMethod());
                    generator.MarkLabel(endIfLabel);
                }
            }
            generator.Emit(OpCodes.Ldloc, result);
            generator.Emit(OpCodes.Ret);
            return method.CreateDelegate(typeof(Func<DataRow, Entity>)) as Func<DataRow, Entity>;
        }
    }

    static class LambdaMethods
    {
        public static readonly MethodInfo StringContainsMethod = typeof(string).GetMethod("Contains", new Type[] { typeof(string) });
        public static readonly MethodInfo ArrayContainsMethod = typeof(Enumerable).GetGenericMethod("Contains", new Type[] { typeof(IEnumerable<>), typeof(object) });

        public static readonly MethodInfo getValueMethod = typeof(DataRow).GetMethod("get_Item", new Type[] { typeof(int) });
        public static readonly MethodInfo isDBNullMethod = typeof(DataRow).GetMethod("IsNull", new Type[] { typeof(int) });

        /// <summary>
        /// 获取泛型的执行方法
        /// </summary>
        /// <param name="targetType"></param>
        /// <param name="name"></param>
        /// <param name="paramterTypes"></param>
        /// <returns></returns>
        public static MethodInfo GetGenericMethod(this Type targetType, string name, params Type[] paramterTypes)
        {
            var methods = targetType.GetMethods().Where(m => m.Name == name && m.IsGenericMethod);
            foreach (var method in methods)
            {
                // 1. 要求函数参数数量相同
                var paramters = method.GetParameters();
                if (paramters.Length != paramterTypes.Length) continue;

                // 2.要求参数类型相同
                int i = 0;
                for (; i < paramters.Length; i++)
                {
                    var paramType = paramters[i].ParameterType;
                    // 如果是泛型参数，那么要求泛型参数的类型跟传入参数的类型相同
                    if (paramType.IsGenericType && paramType.GetGenericTypeDefinition().Equals(paramterTypes[i]) || paramType.Equals(paramterTypes[i]))
                    {
                        continue;
                    }
                    break;
                }

                if (i == paramters.Length)
                {
                    return method;
                }
            }
            return null;
        }
    }


}
